// ********************** МАКРОСЫ ******************************************************************
    // Описывает процесс модуля выделения фронтов сигнала. 
    // Так же описывает создание новых сигналов с именем <имя сигнала> + <_rise> (<_fall>, <_edge>)
    `define front(clk, name)                    \
    logic name``_0;                             \
    logic name``_1;                             \
    logic name``_rise;                          \
    logic name``_fall;                          \
    logic name``_edge;                          \
    always_ff @(posedge clk)                    \
    begin                                       \
        name``_0 <= name;                       \
        name``_1 <= name``_0;                   \
        name``_rise <= name``_0 & (~name``_1);  \
        name``_fall <= name``_1 & (~name``_0);  \
        name``_edge <= name``_0 ^ name``_1;     \
    end

module tx_eth
(
    input  logic            reset_n,
    input  logic            clk,

    input  logic            enet_link100,

    input  logic            enet_tx_clk,

    output logic            enet_rst_n,
    output logic            enet_gtx_clk,
    
    output logic [3:0]      enet_tx_data,
    output logic            enet_tx_en,
    output logic            enet_tx_er,
    
    input  logic [15:0]     data_i,
    input  logic            data_wre,
    
    input  logic            vhdata,
    input  logic            sof,
    input  logic [15:0]     data_size
);

// ********************* КОНСТАНТЫ *****************************************************************

    
// ********************* ТИПЫ **********************************************************************    
    typedef struct packed 
    {   
        logic [3:0]         version;            // Версия
        logic [3:0]         header_length;      // длина заголовка
        logic [7:0]         type_servise;       // Тип обслуживания
        logic [15:0]        total_length;       // Общая длина
        
        logic [15:0]        identification;     // Идентификация (mac?)
        logic [2:0]         flags;              // Флаги
        logic [12:0]        fragment_offset;    // Смещение фрагмента
        
        logic [7:0]         time_live;          // Время жизни
        logic [7:0]         protocol;           // Протокол
        logic [15:0]        header_checksum;    // Контрольная сумма заголовка
        
        logic [31:0]        source;             // IP источника
        logic [31:0]        destination;        // IP назначения
    } ip_header_t;

    typedef struct packed 
    {   
        logic [15:0]        source_port;        // Порт источника
        logic [15:0]        destination_port;   // Порт назначения
        
        logic [15:0]        udp_length;         // длина UDP
        logic [15:0]        udp_crc;            // Контрольная сумма UDP
    } udp_header_t;
    
    typedef struct packed 
    {   
        logic [7:0]         m_id;               // Высота растра
        logic [23:0]        res;                // Резерв
    } mes_header_t;
    
    typedef struct packed 
    {   
        logic [15:0]        heigtht;            // Высота растра
        logic [15:0]        width;              // Ширина растра
        logic [15:0]        frame;              // Номер видеокадра
        logic [15:0]        row;                // Номер строки
        logic [15:0][3:0]   res;                // Резерв
    } video_header_t;
                    
    enum logic [10:0]    {   IDLE_s,
                            SEND_5h_s,
                            SEND_HEADER_s,
                            SEND_BROADCAST_s,
                            SEND_DATA_s,
                            SEND_MAC_s,
                            SEND_TYPE_s,
                            SEND_MH_s,
                            SEND_VH_s,
                            SEND_CRC_s,
                            WAIT_s
                    } state_send_frame;
                    
                    
// ********************* ПЕРЕМЕННЫЕ ****************************************************************
    ip_header_t         ip_header;
    udp_header_t        udp_header;
    mes_header_t        mes_header;
    video_header_t      video_header;
    
    logic [0:55][3:0]   datagramm;
    logic [0:31][3:0]   video_header_terad;
    logic [0:7][3:0]    mes_header_tetrad;

    logic [15:0]        data_cnt;
    logic [15:0]        length;
    logic [3:0][3:0]    type_ip;
    logic [0:11][3:0]   mac;
    logic [0:7][3:0]    crc32_tetrad;
    logic [31:0]        crc32;
    logic [0:31]        crc32_r;
    
    logic               rdreq;
    logic [3:0]         data_read;
    logic               start;
    logic [15:0]        col;
    
    
    logic               sof_long;
    logic               current_bank;
    
//  ********************* БЛОК НЕПРЕРЫВНЫХ НАЗНАЧЕНИЙ ASSIGN ***************************************
    assign enet_gtx_clk    = 1'b0;
    assign enet_rst_n      = reset_n;
    assign crc32_tetrad    = crc32_r;
    assign crc32_r    = crc32;
// ********************* ФУНКЦИИ ******************************************************************   
    function [31:0] nextCRC32_D4 
    (
        logic [3:0] Data, 
        logic [31:0] crc
    );
    
    logic [3:0]     d;
    logic [31:0]    c;
    logic [31:0]    newcrc;
    
        begin
            d = Data;
            c = crc;

            newcrc[0] = d[0] ^ c[28];
            newcrc[1] = d[1] ^ d[0] ^ c[28] ^ c[29];
            newcrc[2] = d[2] ^ d[1] ^ d[0] ^ c[28] ^ c[29] ^ c[30];
            newcrc[3] = d[3] ^ d[2] ^ d[1] ^ c[29] ^ c[30] ^ c[31];
            newcrc[4] = d[3] ^ d[2] ^ d[0] ^ c[0] ^ c[28] ^ c[30] ^ c[31];
            newcrc[5] = d[3] ^ d[1] ^ d[0] ^ c[1] ^ c[28] ^ c[29] ^ c[31];
            newcrc[6] = d[2] ^ d[1] ^ c[2] ^ c[29] ^ c[30];
            newcrc[7] = d[3] ^ d[2] ^ d[0] ^ c[3] ^ c[28] ^ c[30] ^ c[31];
            newcrc[8] = d[3] ^ d[1] ^ d[0] ^ c[4] ^ c[28] ^ c[29] ^ c[31];
            newcrc[9] = d[2] ^ d[1] ^ c[5] ^ c[29] ^ c[30];
            newcrc[10] = d[3] ^ d[2] ^ d[0] ^ c[6] ^ c[28] ^ c[30] ^ c[31];
            newcrc[11] = d[3] ^ d[1] ^ d[0] ^ c[7] ^ c[28] ^ c[29] ^ c[31];
            newcrc[12] = d[2] ^ d[1] ^ d[0] ^ c[8] ^ c[28] ^ c[29] ^ c[30];
            newcrc[13] = d[3] ^ d[2] ^ d[1] ^ c[9] ^ c[29] ^ c[30] ^ c[31];
            newcrc[14] = d[3] ^ d[2] ^ c[10] ^ c[30] ^ c[31];
            newcrc[15] = d[3] ^ c[11] ^ c[31];
            newcrc[16] = d[0] ^ c[12] ^ c[28];
            newcrc[17] = d[1] ^ c[13] ^ c[29];
            newcrc[18] = d[2] ^ c[14] ^ c[30];
            newcrc[19] = d[3] ^ c[15] ^ c[31];
            newcrc[20] = c[16];
            newcrc[21] = c[17];
            newcrc[22] = d[0] ^ c[18] ^ c[28];
            newcrc[23] = d[1] ^ d[0] ^ c[19] ^ c[28] ^ c[29];
            newcrc[24] = d[2] ^ d[1] ^ c[20] ^ c[29] ^ c[30];
            newcrc[25] = d[3] ^ d[2] ^ c[21] ^ c[30] ^ c[31];
            newcrc[26] = d[3] ^ d[0] ^ c[22] ^ c[28] ^ c[31];
            newcrc[27] = d[1] ^ c[23] ^ c[29];
            newcrc[28] = d[2] ^ c[24] ^ c[30];
            newcrc[29] = d[3] ^ c[25] ^ c[31];
            newcrc[30] = c[26];
            newcrc[31] = c[27];
            nextCRC32_D4 = newcrc;
        end
    endfunction

// ********************* ПРОЦЕССЫ ******************************************************************
    `front (enet_tx_clk, vhdata)
    `front (enet_tx_clk, start)  
    `front (enet_tx_clk, sof_long)
    `front (clk, sof)   
    `front (clk, data_wre) 
     
    always_ff @(posedge clk)
    begin
        if (vhdata)
            col <= 16'd0;
        else if (data_wre_fall)
            col <= col + 16'd1;
        else;
        
        if (sof_rise)
            sof_long  <= ~sof_long;
        else;
    end
    
    always_ff @(posedge enet_tx_clk)
    begin    
        start <= vhdata_rise;
        if (vhdata_rise) begin
            current_bank    <= ~current_bank;
        end
        else;
    end
    
    always_ff @(posedge enet_tx_clk)
    begin
        ip_header.version           <= 4'd4;
        ip_header.header_length     <= 4'd5;
        ip_header.type_servise      <= 8'd0;
        ip_header.total_length      <= length + 16'd44;
        ip_header.identification    <= 16'h0279;
        ip_header.flags             <= 4'd0;
        ip_header.fragment_offset   <= 13'd0;
        ip_header.time_live         <= 8'd128;
        ip_header.protocol          <= 8'd17;
        ip_header.header_checksum   <= 16'h0;
        
        ip_header.source            <= 32'hc0_a8_c3_01; //192.168.195.1 
        ip_header.destination       <= 32'hFF_FF_FF_FF; // broadcast

        udp_header.source_port      <=  16'd703;
        udp_header.destination_port <=  16'd703;
        udp_header.udp_length       <=  length + 16'd24;
        udp_header.udp_crc          <=  16'h00_00;
                
        datagramm       <= {ip_header, udp_header};
        type_ip         <= {4'h0, 4'h0, 4'h0, 4'h8};
        // mac             <= {4'hb, 4'h6, 4'h9, 4'hd, 4'h8, 4'h9, 4'h1, 4'h8, 4'h2, 4'h6, 4'h0, 4'h0};
        // mac             <= {4'h9, 4'h0, 4'hf, 4'ha, 4'h8, 4'h2, 4'h0, 4'h8, 4'he, 4'hd, 4'h4, 4'h9};
        mac             <= {8'h94, 8'hde, 8'h80, 8'h28, 8'haf, 8'h90};
        
        // Заголовок пакета с типом сообщения
        mes_header.m_id <= 8'd5;
        mes_header.res  <= 24'h123456;
        
        mes_header_tetrad   <= mes_header;
        
        // Свой заголовок видеокадров
        video_header.heigtht     = 16'd240;
        video_header.width       = 16'd320;
        
        if (sof_long_rise) begin
            video_header.frame   = 16'd1;
            // video_header.frame   = video_header.frame + 16'd1;
            video_header.row     = 16'd0;
        end
        else;
        
        if (start_rise)
            // video_header.row         = video_header.row + 16'd1;
            video_header.row         = 16'd1;
        else;
        video_header.res    <= {16{4'hf}};
        
        video_header_terad  <= video_header;
        

    end

    
  
    always_ff @(posedge enet_tx_clk)
    begin
        if (!reset_n) begin
            state_send_frame <= IDLE_s;
        end
        else begin
            data_cnt    <= data_cnt + 16'd1;
            enet_tx_en  <= 1'b1;
            rdreq       <= 1'b0;
            crc32       <= 32'hFFFFFFFF;
            case (state_send_frame)
                IDLE_s: begin
                    data_cnt    <= 16'd0;
                    enet_tx_en  <= 1'b0;
                    if (start_rise) begin   // Накопив 320 отсчетов их надо передать
                        length  <= (data_size << 1);
                        state_send_frame <= SEND_5h_s;
                    end
                    else;
                end
                
                SEND_5h_s: begin
                    enet_tx_data   <= 4'h5;
                    if (data_cnt >= 16'd7) begin
                        enet_tx_data   <= 4'hD;
                        data_cnt        <= 16'd0;
                        state_send_frame <= SEND_BROADCAST_s;
                    end
                    else;
                end
                
                SEND_BROADCAST_s: begin
                    enet_tx_data   <= 4'hF;
                    if (data_cnt >= 16'd1)
                        crc32 <= nextCRC32_D4(enet_tx_data, crc32);
                    else;
                    
                    if (data_cnt >= 16'd11) begin
                        state_send_frame <= SEND_MAC_s;
                        data_cnt         <= 16'd0;
                    end
                    else;
                end

                SEND_MAC_s: begin
                    enet_tx_data    <= mac[{data_cnt[15:1], ~data_cnt[0]}];
                    crc32           <= nextCRC32_D4(enet_tx_data, crc32);
                    if (data_cnt >= 16'd11) begin
                        data_cnt        <= 16'd0;
                        state_send_frame <= SEND_TYPE_s;
                    end
                    else;
                end
                
                SEND_TYPE_s: begin
                    enet_tx_data    <= type_ip[data_cnt];
                    crc32           <= nextCRC32_D4(enet_tx_data, crc32);
                    if (data_cnt >= 16'd3) begin
                        data_cnt        <= 16'd0;
                        state_send_frame <= SEND_HEADER_s;
                    end
                    else;
                end       
                
                SEND_HEADER_s: begin
                    enet_tx_data    <= datagramm[{data_cnt[15:1], ~data_cnt[0]}];
                    crc32           <= nextCRC32_D4(enet_tx_data, crc32);
                    if (data_cnt >= 16'd55) begin
                        state_send_frame <= SEND_MH_s;
                        data_cnt         <= 16'd0;
                    end
                    else;
                end
                
                SEND_MH_s: begin
                    enet_tx_data    <= mes_header_tetrad[{data_cnt[15:1], ~data_cnt[0]}];
                    crc32           <= nextCRC32_D4(enet_tx_data, crc32);
                    if (data_cnt >= 16'd7) begin
                        state_send_frame <= SEND_VH_s;
                        data_cnt         <= 16'd0;
                    end
                    else;
                end
                
                SEND_VH_s: begin
                    enet_tx_data    <= video_header_terad[{data_cnt[15:1], ~data_cnt[0]}];
                    crc32           <= nextCRC32_D4(enet_tx_data, crc32);
                    if (data_cnt >= 16'd31) begin
                        state_send_frame <= SEND_DATA_s;
                        // data_cnt         <= 16'd0;
                    end
                    else;
                end
                
                SEND_DATA_s: begin
                    enet_tx_data    <= data_read;
                    crc32           <= nextCRC32_D4(enet_tx_data, crc32);
                    rdreq   <= 1'b1;
                    if (data_cnt >= ((length << 1) + 16'd30)) begin
                        rdreq   <= 1'b0;
                        data_cnt    <= 16'd0;
                        state_send_frame <= SEND_CRC_s;
                    end
                    else;
                end
             
                SEND_CRC_s: begin
                    enet_tx_data   <= crc32_tetrad[{data_cnt[15:1], ~data_cnt[0]}];
                    if (data_cnt == 16'd0)
                        crc32 <= nextCRC32_D4(enet_tx_data, crc32);
                    else;
                    
                    rdreq   <= 1'b1;
                    if (data_cnt >= 16'd7) begin
                        data_cnt    <= 16'd0;
                        state_send_frame <= WAIT_s;
                    end
                    else;
                end
                
                WAIT_s: begin
                    if (data_cnt >= 16'd6) begin
                        state_send_frame <= IDLE_s;
                    end
                    else;
                end
                
                default: 
                    enet_tx_en     <= 1'b0;
            endcase
        end
    end

// ********************* ПОДКЛЮЧАЕМЫЕ МОДУЛИ *******************************************************
    // Память для принятия входного пакета
    altsyncram
    #(  
        .address_aclr_b         ("NONE"),
        .address_reg_b          ("CLOCK1"),
        .clock_enable_input_a   ("BYPASS"),
        .clock_enable_input_b   ("BYPASS"),
        .clock_enable_output_b  ("BYPASS"),
        .intended_device_family ("Cyclone IV E"),
        .lpm_type               ("altsyncram"),
        .numwords_a             (1024),
        .numwords_b             (4096),
        .operation_mode         ("DUAL_PORT"),
        .outdata_aclr_b         ("NONE"),
        .outdata_reg_b          ("CLOCK1"),
        .power_up_uninitialized ("FALSE"),
        .widthad_a              (10),
        .widthad_b              (12),
        .width_a                (16),
        .width_b                (4)
        // .width_byteena_a        (1)
    )
    altsyncram_component 
    (
        .clock0                 (clk),
        .address_a              ({current_bank, col[9:0]}),
        .data_a                 ({col[15:12], col[11:8],  col[7:4], col[3:0]}),//16'h1234)
        .wren_a                 (data_wre),
        
        .clock1                 (enet_tx_clk),        
        // .address_b              ({~current_bank, {data_cnt[11:1], ~data_cnt[0]}} - 16'd30),
        .address_b              ({~current_bank, data_cnt[11:0]} - 16'd30),
        .q_b                    (data_read)
        // .rden_b                 (rdreq)
    );
    
endmodule
